<?php
namespace Jwt;
use Cache\Redis;
use Config;
use Exception;
use RedisException;

class Token
{
    /**
     * @var string
     */
    public static string $prefix_refresh = 'refresh_token:';
    /**
     * @var string
     */
    public static string $prefix_member = 'member_token:';

    /**
     * access_token 过期时间单位小时
     * @var string
     */
    protected static string $expires_access = '+300 day';// 30 day 1 minutes

    /**
     * refresh_token 过期时间单位小时
     * @var string
     */
    protected static string $expires_refresh = '+600 day';

    /**
     * @var int
     */
    protected static int $status = 0;

    /**
     * @var string
     */
    protected static string $msg = '';


    /**
     * 生成token
     * @param $member_info
     * @return array|bool
     * @throws Exception
     */
    public static function generate($member_info):array|bool
    {
        /** 处理用户数据 */
        $member_info = static::memberInfo($member_info);
        $member_id = $member_info['id'];
        unset($member_info['id']);
        $access_token = static::accessToken($member_id,$member_info);
        if ($access_token === false) return false;
        $refresh_token = static::refreshToken($access_token['token_id'],$member_id);
        if ($refresh_token === false) return false;
        return [
            'token_type' => 'Bearer',
            'expires_time' => $access_token['expires_time'],
            'access_token' => $access_token['access_token'],
            'refresh_token' => $refresh_token,
        ];
    }

    /**
     * access_token
     * @param int $member_id
     * @param array $member_info
     * @return array|bool
     * @throws Exception
     */
    public static function accessToken(int $member_id, array $member_info): array|bool
    {
        $token_data = [
            'id'=>md5($member_id.time()),
            'member_id'=>$member_id,
            'add_time'=> date("Y-m-d H:i:s"),
            'expires_time'=>date("Y-m-d H:i:s",strtotime(static::$expires_access)),
        ];
        $sign = static::sign($token_data);
        $access_token = base64_encode(json_encode($token_data)) . '.' . $sign;
        $res = Redis::getRedis()->set(self::$prefix_member.$member_id,array_merge($token_data,$member_info));
        if ($res) {
            return [
                'access_token' => $access_token,
                'expires_time' => $token_data['expires_time'],
                'token_id' => $token_data['id']
            ];
        }else {
            self::$status = 4;
            self::$msg = 'token 生成失败，请稍后重试';
            return false;
        }
    }

    /**
     * refresh_token
     * @param string $token_id
     * @param int $member_id
     * @return bool|string
     * @throws Exception
     */
    protected static function refreshToken(string $token_id,int $member_id): bool|string
    {
        $token_data = [
            'id'=>md5($token_id.$member_id.time()),
            'member_id'=>$member_id,
            'add_time'=> date("Y-m-d H:i:s"),
            'expires_time'=>date("Y-m-d H:i:s",strtotime(static::$expires_refresh)),
        ];
        $sign = static::sign($token_data);
        $res = Redis::getRedis()->set(self::$prefix_refresh.$member_id,$token_data);
        if ($res) {
            return base64_encode(json_encode($token_data)) . '.' . $sign;
        }else {
            self::$status = 4;
            self::$msg = 'token 生成失败，请稍后重试';
            return false;
        }
    }

    /**
     * 生成签名
     * @param array $data
     * @return string
     * @throws Exception
     */
    protected static function sign(array $data):string
    {
        return Rsa::generate_sign($data, static::get_private_key());
    }

    /**
     * 获取公钥
     * @return string
     * @throws Exception
     */
    public static function get_public_key(): string
    {
        $publicKey = Config::get("jwt")['publicKey'];
        if (!file_exists($publicKey)) throw new Exception('RSA公钥错误。请检查公钥文件格式是否正确');
        return file_get_contents($publicKey);

    }

    /**
     * 获取私钥
     * @return string
     * @throws Exception
     */
    public static function get_private_key():string
    {
        $privateKey = Config::get("jwt")['privateKey'];
        if (!file_exists($privateKey))  throw new Exception('您使用的私钥格式错误，请检查RSA私钥配置');
        return file_get_contents($privateKey);

    }

    /**
     * 校验签名
     * @param array $token_data
     * @param string $sign
     * @return boolean
     * @throws Exception
     */
    protected static function verifySign(array $token_data, string $sign): bool
    {
        return Rsa::verify_sign($token_data, $sign, static::get_public_key());
    }

    /**
     *  获取 token
     * @return bool|string
     */
    protected static function getToken(): bool|string
    {
        if (function_exists('apache_request_headers')) {
            $headers = apache_request_headers();
            if (!isset($headers['authorization'])&&!isset($headers['Authorization'])) return false;
            $authorization = $headers['authorization']??$headers['Authorization'];
        } else {
            if (!isset($_SERVER['HTTP_AUTHORIZATION'])) return false;
            $authorization = $_SERVER['HTTP_AUTHORIZATION'];
        }
        return trim(preg_replace('/^(?:\s+)?Bearer\s/', '', $authorization));
    }

    /**
     * 用户数据
     * @param array $member_info
     * @param string $field
     * @return array
     * @throws Exception
     */
    public static function memberInfo(array $member_info, string $field = 'id'):array
    {
        if (!isset($member_info[$field])) throw new Exception ( '用户数据异常');
        if (!isset($member_info['nickname'])) throw new Exception ( '用户数据异常');
        if (!isset($member_info['mobile'])) throw new Exception ( '用户数据异常');
        return [
            'id' => $member_info[$field],
            'nickname' => $member_info['nickname'],
            'mobile' => $member_info['mobile'],
            'is_hid'=>$member_info['is_hid']??0,
        ];
    }


    /**
     * 校验token，获取用信息
     * @return bool|array
     * @throws Exception
     */
    public static function verify(): bool|array
    {
        $token = static::getToken();
        if ($token) {
            list($token_data, $sign) = explode('.', $token);
            $token_data = json_decode(base64_decode($token_data), true);
            $verify = static::verifySign($token_data, $sign);
            if ($verify) {
                return static::verifyAccessToken($token_data);
            }
        }
        static::$status = 4;
        static::$msg = 'token 错误';
        return false;
    }

    /**
     * 校验 access token
     * @param array $token_data
     * @return bool|array
     * @throws RedisException
     */
    protected static function verifyAccessToken(array $token_data): bool|array
    {
        if ($_data = Redis::getRedis()->get(self::$prefix_member.$token_data['member_id'])) {
            if ($token_data['id'] == $_data['id']) {
                if (date('Y-m-d H:i:s') < $token_data['expires_time']) return $_data;
                static::$status = 2;
                static::$msg = 'token 已失效';
            }else {
                static::$status = 3;
                static::$msg = '您已在其它地方登陆';
            }
            return false;
        }
        static::$status = 4;
        static::$msg = 'token 错误';
        return false;
    }

    /**
     * 刷新 token
     * @param string $token
     * @return bool|array
     * @throws Exception
     */
    public static function refresh(string $token): bool|array
    {
        list($token_data, $sign) = explode('.', $token);
        $token_data = json_decode(base64_decode($token_data), true);
        $verify = static::verifySign($token_data, $sign);
        if ($verify) {
            return static::verifyRefreshToken($token_data);
        }
        static::$status = 4;
        static::$msg = 'token 错误';
        return false;
    }

    /**
     * 校验 refresh token
     * @param array $token_data
     * @return bool|array
     * @throws Exception
     */
    protected static function verifyRefreshToken(array $token_data): bool|array
    {
        $key = self::$prefix_refresh.$token_data['member_id'];
        $refresh_data = Redis::getRedis()->get($key);
        if ($refresh_data) {
            $key1 = self::$prefix_member.$token_data['member_id'];
            $member_token = Redis::getRedis()->get($key1);
            if ($member_token) {
                if ($token_data['id'] == $refresh_data['id']) {
                    if (date('Y-m-d H:i:s') < $refresh_data['expires_time']) {
                        Redis::getRedis()->del($key,$key1);
                        return static::generate([
                            'id'=>$member_token['member_id'],
                            'mobile'=>$member_token['mobile'],
                            'nickname'=>$member_token['nickname'],
                        ]);
                    }
                    static::$status = 2;
                    static::$msg = 'token 已失效';
                    return false;
                }
                static::$status = 3;
                static::$msg = '您的账号已在其它地方登陆';
                return false;
            }
        }
        static::$status = 4;
        static::$msg = 'token 错误';
        return false;
    }

    /**
     * 设置 expires_access
     * @param string $value
     * @param string $unit
     */
    public static function setExpiresAccess(string $value, string $unit = 'hours'): void
    {
        static::$expires_access = $value . ' ' . $unit;
    }

    /**
     * 设置 expires_refresh
     * @param string $value
     * @param string $unit
     */
    public static function setExpiresRefresh(string $value, string $unit = 'hours'): void
    {
        static::$expires_refresh = $value . ' ' . $unit;
    }

    /**
     * 获取 expires_access
     * @return string
     */
    public static function getExpiresAccess(): string
    {
        return static::$expires_access;
    }

    /**
     * 获取 expires_refresh
     * @return string
     */
    public static function getExpiresRefresh(): string
    {
        return static::$expires_refresh;
    }


    /**
     * 获取状态
     * @return int
     */
    public static function get_status(): int
    {
        return static::$status;
    }

    /**
     * 获取错误信息
     * @return string
     */
    public static function get_msg(): string
    {
        return static::$msg;
    }
}
